#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using UTable.Input;
using UTable.Input.MultiTouch;
using UTable.Objects.Controls.Primitives;
using UTable.Objects.Handlers;

namespace UTable.Objects.Controls
{
    public class UScrollViewer : ScrollViewer, IControl, IControlDisposable
    {
        #region Private Fields

        private UScrollBar horizontalScrollBar;

        private UScrollBar verticalScrollBar;

        private Point _dragStartPosition;

        private double _dragStartHorizontalOffset;

        private double _dragStartVerticalOffset;

        private DispatcherTimer _hideScollbarTimer;

        #endregion

        #region Constructors

        static UScrollViewer()
        {
            // UContentControlImpl.OverrideMetadata(typeof(UScrollViewer));
            DefaultStyleKeyProperty.OverrideMetadata(typeof(UScrollViewer), new FrameworkPropertyMetadata(typeof(UScrollViewer)));
        }

        public UScrollViewer()
        {
            _hideScollbarTimer = new DispatcherTimer();
            _hideScollbarTimer.Interval = TimeSpan.FromMilliseconds(1000);
            _hideScollbarTimer.Tick += new EventHandler(_hideScollbarTimer_Tick);
            this.InputReceived += new InputEventHandler(UScrollViewer_InputReceived);

            InitializeIControlPart();

            OnModeChanged();

            //PropertyChangeNotifier notifier = new PropertyChangeNotifier(this, "HorizontalOffset");
            //notifier.ValueChanged += new EventHandler(OnScrollValueChanged);
            //PropertyChangeNotifier notifier2 = new PropertyChangeNotifier(this, "VerticalOffset");
            //notifier2.ValueChanged += new EventHandler(OnScrollValueChanged);

            DependencyPropertyDescriptor.FromProperty(UScrollViewer.HorizontalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnScrollValueChanged));
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.VerticalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnScrollValueChanged));

        }

        void UScrollViewer_Loaded(object sender, RoutedEventArgs e)
        {
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.HorizontalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnScrollValueChanged));
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.VerticalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnScrollValueChanged));

            DependencyPropertyDescriptor.FromProperty(UScrollViewer.MHorizontalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnMHorizontalOffsetChanged));
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.MVerticalOffsetProperty,
                    typeof(UScrollViewer)).AddValueChanged(this, new EventHandler(OnMVerticalOffsetChanged));
        }

        private static void OnDependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            UScrollViewer viewer = obj as UScrollViewer;
            if (args.Property == HorizontalOffsetProperty || args.Property == VerticalOffsetProperty)
                viewer.OnScrollValueChanged(viewer, null);
            else if (args.Property == MHorizontalOffsetProperty)
                viewer.OnMHorizontalOffsetChanged(viewer, null);
            else if (args.Property == MVerticalOffsetProperty)
                viewer.OnMVerticalOffsetChanged(viewer, null);
        }

        #endregion

        #region Private Methods

        private void UScrollViewer_InputReceived(InputEventArgs args)
        {
            if (args is MultiTouchEventArgs)
            {
                args.Handled = true;
				if (this.Mode == UScrollViewerMode.ScrollByContent)
					args.RouteDown = false;
            }
        }

        private void Handler_GestureDetected(MultiTouchEventHandlerClass sender, SingleFingerGestureEventArgs args)
        {
            switch (args.Type)
            {
                case SingleFingerGestureType.DragStarted:
                    OnContentDragStarted(args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.Dragging:
                    OnContentDragging(args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.DragEnded:
                    OnContentDragEnded(args as SingleFingerGestureDragEventArgs);
                    break;
            }
        }


        private void OnContentDragStarted(SingleFingerGestureDragEventArgs args)
        {
            _dragStartPosition = args.FingerPosition;
            _dragStartHorizontalOffset = this.HorizontalOffset;
            _dragStartVerticalOffset = this.VerticalOffset;

			StopMove();
        }


        private void OnContentDragging(SingleFingerGestureDragEventArgs args)
        {
            Vector offset = args.FingerPosition - _dragStartPosition;
            this.ScrollToHorizontalOffset(_dragStartHorizontalOffset - offset.X);
            this.ScrollToVerticalOffset(_dragStartVerticalOffset - offset.Y);
        }


        private void OnContentDragEnded(SingleFingerGestureDragEventArgs args)
        {
			Move(args.Velocity);
        }

		private void Move(Vector velocity)
		{
			if (double.IsNaN(velocity.X) || double.IsNaN(velocity.Y))
				return;
			double decelerationX = 10;
			double decelerationY = 10;
			double moveX;
			double moveY;
			double timeX;
			double timeY;
			double startVelocityX = velocity.X;
			double startVelocityY = velocity.Y;
			double scrollXDistance;
			double scrollYDistance;
			if (startVelocityX > 0)
			{
				// scroll right
				scrollXDistance = HorizontalOffset;
			}
			else
			{
				scrollXDistance = ScrollableWidth - HorizontalOffset;
			}
			if (startVelocityY > 0)
			{
				// scroll down
				scrollYDistance = VerticalOffset;
			}
			else
			{
				scrollYDistance = ScrollableHeight - VerticalOffset;
			}

			double endVelocityX = PhysicsTracker.DecelerateSomeDistance(startVelocityX, decelerationX, scrollXDistance, out timeX, out moveX);
			double endVelocityY = PhysicsTracker.DecelerateSomeDistance(startVelocityY, decelerationY, scrollYDistance, out timeY, out moveY);

			double desOffsetX = HorizontalOffset - moveX * 1.5 * Math.Sign(startVelocityX);
			double desOffsetY = VerticalOffset - moveY * 1.5 * Math.Sign(startVelocityY);

			// do animation
			this.MHorizontalOffset = this.HorizontalOffset;
			this.MVerticalOffset = this.VerticalOffset;
			if (ScrollableWidth > 0)
				AnimationUtility.DoDoubleAnimation(desOffsetX, TimeSpan.FromSeconds(timeX), UScrollViewer.MHorizontalOffsetProperty, this, true);
			if (ScrollableHeight > 0)
				AnimationUtility.DoDoubleAnimation(desOffsetY, TimeSpan.FromSeconds(timeY), UScrollViewer.MVerticalOffsetProperty, this, true);
		}

		private void StopMove()
		{
			// stop animation
			if (this.ScrollableHeight > 0)
			{
				this.BeginAnimation(UScrollViewer.MVerticalOffsetProperty, null);
				MVerticalOffset = VerticalOffset;
			}
			if (this.ScrollableWidth > 0)
			{
				this.BeginAnimation(UScrollViewer.MHorizontalOffsetProperty, null);
				MHorizontalOffset = HorizontalOffset;
			}
		}

        private static void OnModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as UScrollViewer).OnModeChanged();
        }

        private void OnModeChanged()
        {
            if (this.Mode == UScrollViewerMode.ScrollByBar)
            {
                this.MultiTouchProcessor.Handlers.Clear();
				_hideScollbarTimer.Stop();
				StopMove();
            }
            else if (this.Mode == UScrollViewerMode.ScrollByContent)
            {
                SingleFingerGestureHandler handler = new SingleFingerGestureHandler(this, this);
				handler.TrackVelocity = true;
                handler.GestureDetected += new SingleFingerGestureEventHandler(Handler_GestureDetected);
                this.MultiTouchProcessor.Handlers.Add(handler);
            }
        }

        private void OnScrollValueChanged(object sender, EventArgs args)
        {
            this.ShowScrollBar = true;
			if (this.ScrollableWidth > 0)
				this.horizontalScrollBar.Value = this.HorizontalOffset * (horizontalScrollBar.Maximum - horizontalScrollBar.Minimum) / this.ScrollableWidth + horizontalScrollBar.Minimum;
			if (this.ScrollableHeight > 0)
				this.verticalScrollBar.Value = this.VerticalOffset * (verticalScrollBar.Maximum - verticalScrollBar.Minimum) / this.ScrollableHeight + verticalScrollBar.Minimum;

            // hide the scrollbar if needed
            if (this.AutoHideScrollBar && this.Mode == UScrollViewerMode.ScrollByContent)
            {
                if (this._hideScollbarTimer.IsEnabled)
                {
                    _hideScollbarTimer.Stop();
                }
                _hideScollbarTimer.Start();
            }
        }

        void _hideScollbarTimer_Tick(object sender, EventArgs e)
        {
            if (this._hideScollbarTimer.IsEnabled)
            {
                _hideScollbarTimer.Stop();
                ShowScrollBar = false;
            }
        }

        void verticalScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
			if (this.Mode == UScrollViewerMode.ScrollByBar)
			{
				double offset = 0;
				// calculate the offset
				offset = (verticalScrollBar.Value - verticalScrollBar.Minimum) /
					(verticalScrollBar.Maximum - verticalScrollBar.Minimum) * this.ScrollableHeight;

				this.MVerticalOffset = offset;
				this.ScrollToVerticalOffset(offset);
			}
        }

        void horizontalScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
			if (this.Mode == UScrollViewerMode.ScrollByBar)
			{
				double offset = 0;
				// calculate the offset
				offset = (horizontalScrollBar.Value - horizontalScrollBar.Minimum) /
					(horizontalScrollBar.Maximum - horizontalScrollBar.Minimum) * this.ScrollableWidth;
				this.MHorizontalOffset = offset;
				this.ScrollToHorizontalOffset(offset);
			}
        }

		private void OnMHorizontalOffsetChanged(object sender, EventArgs args)
		{
			this.ScrollToHorizontalOffset(this.MHorizontalOffset);
		}

		private void OnMVerticalOffsetChanged(object sender, EventArgs args)
		{
			this.ScrollToVerticalOffset(this.MVerticalOffset);
		}

        #endregion

        #region Properties

        public UScrollViewerMode Mode
        {
            get { return (UScrollViewerMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(UScrollViewerMode), typeof(UScrollViewer), new UIPropertyMetadata(UScrollViewerMode.ScrollByContent, new PropertyChangedCallback(OnModeChanged)));

        /// <summary>
        /// For ScrollByContent mode.
        /// Indicates whether the scrollbar should be shown.
        /// When the content is scrolling, the scroll bar will show, otherwise hide
        /// </summary>
        internal bool ShowScrollBar
        {
            get { return (bool)GetValue(ShowScrollBarProperty); }
            set { SetValue(ShowScrollBarProperty, value); }
        }

        internal static readonly DependencyProperty ShowScrollBarProperty =
            DependencyProperty.Register("ShowScrollBar", typeof(bool), typeof(UScrollViewer), new UIPropertyMetadata(false));


        /// <summary>
        /// Indicate whether to hide the scrollbar automatically
        /// If true, then the scrollbar will hide if the content is not scrolling.
        /// 
        /// This value works only in ScrollByContent mode. In ScrollByBar mode, the scrollbar will 
        /// always show if needed
        /// </summary>
        public bool AutoHideScrollBar
        {
            get { return (bool)GetValue(AutoHideScrollBarProperty); }
            set { SetValue(AutoHideScrollBarProperty, value); }
        }

        public static readonly DependencyProperty AutoHideScrollBarProperty =
            DependencyProperty.Register("AutoHideScrollBar", typeof(bool), typeof(UScrollViewer), new UIPropertyMetadata(true));

        /// <summary>
        /// Used to do animation on HoziaontalOffset,
        /// the HorizontalOffset property does not provide a setter method
        /// </summary>
		public double MHorizontalOffset
		{
			get { return (double)GetValue(MHorizontalOffsetProperty); }
			set { SetValue(MHorizontalOffsetProperty, value); }
		}

		public static readonly DependencyProperty MHorizontalOffsetProperty =
            DependencyProperty.Register("MHorizontalOffset", typeof(double), typeof(UScrollViewer), new UIPropertyMetadata(0.0, new PropertyChangedCallback(OnDependencyPropertyChanged)));

        /// <summary>
        /// Used to do animation on VerticalOffset,
        /// the VerticalOffset property does not provide a setter method
        /// </summary>
		public double MVerticalOffset
		{
			get { return (double)GetValue(MVerticalOffsetProperty); }
			set { SetValue(MVerticalOffsetProperty, value); }
		}

		public static readonly DependencyProperty MVerticalOffsetProperty =
            DependencyProperty.Register("MVerticalOffset", typeof(double), typeof(UScrollViewer), new UIPropertyMetadata(0.0, new PropertyChangedCallback(OnDependencyPropertyChanged)));


        #endregion

        #region Public Methods

        public void DisposeControl()
        {
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.HorizontalOffsetProperty,
                    typeof(UScrollViewer)).RemoveValueChanged(this, new EventHandler(OnScrollValueChanged));
            DependencyPropertyDescriptor.FromProperty(UScrollViewer.VerticalOffsetProperty,
                    typeof(UScrollViewer)).RemoveValueChanged(this, new EventHandler(OnScrollValueChanged));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // load the templates
            horizontalScrollBar = this.GetTemplateChild("PART_HorizontalScrollBar") as UScrollBar;
            verticalScrollBar = this.GetTemplateChild("PART_VerticalScrollBar") as UScrollBar;
            horizontalScrollBar.ValueChanged += new RoutedPropertyChangedEventHandler<double>(horizontalScrollBar_ValueChanged);
            verticalScrollBar.ValueChanged += new RoutedPropertyChangedEventHandler<double>(verticalScrollBar_ValueChanged);
        }

        #endregion

		#region IControl Implementation
		
		#region Private Fields

        private ControlBaseImpl _impl;

        #endregion

		#region Private Methods

		private void InitializeIControlPart()
        {
			_impl = new ControlBaseImpl(this);
            _impl.GotFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_GotFingerCapture);
            _impl.LostFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_LostFingerCapture);
			this.InputReceived += new UTable.Input.InputEventHandler(_impl_InputReceived);
        }

        void _impl_InputReceived(UTable.Input.InputEventArgs args)
        {
			if (args is UTable.Input.MultiTouch.MultiTouchEventArgs)
				MultiTouchProcessor.RaiseMultiTouchEvent(args as UTable.Input.MultiTouch.MultiTouchEventArgs);
        }

        void _impl_LostFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (LostFingerCapture != null)
                LostFingerCapture(sender, args);
        }

        void _impl_GotFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (GotFingerCapture != null)
                GotFingerCapture(sender, args);
        }

        #endregion

        #region IControl Members

        public UTable.Objects.Controls.ControlInfo Info
        {
            get { return _impl.Info; }
        }
		
		public UTable.Objects.Handlers.MultiTouchProcessor MultiTouchProcessor 
		{ 
			get { return _impl.MultiTouchProcessor; } 
		}

        #endregion

        #region IVisualNode Members

        public String ID
        {
            get { return _impl.ID; }
        }

        public new double Width
        {
            get { return _impl.Width; }
            set { _impl.Width = value; }
        }

        public new double Height
        {
            get { return _impl.Height; }
            set { _impl.Height = value; }
        }

        public new bool IsHitTestVisible
        {
            get { return _impl.IsHitTestVisible; }
            set { _impl.IsHitTestVisible = value; }
        }

        #endregion

        #region IInputNode Members

        public void RaiseEvent(UTable.Input.InputEventArgs e)
        {
            if (InputReceived != null)
            {
                InputReceived(e);
            }
            OnInputReceived(e);
        }

        public virtual void OnInputReceived(UTable.Input.InputEventArgs args)
        {
            _impl.OnInputReceived(args);
        }

        public bool CaptureFinger(int fingerId)
        {
            return _impl.CaptureFinger(fingerId);
        }

        public bool UncaptureFinger(int fingerId)
        {
            return _impl.UncaptureFinger(fingerId);
        }

        public event UTable.Input.MultiTouch.FingerEventHandler GotFingerCapture;

        public event UTable.Input.MultiTouch.FingerEventHandler LostFingerCapture;

        public event UTable.Input.InputEventHandler InputReceived;

        #endregion
        #endregion
    }

    public enum UScrollViewerMode
    {
        ScrollByBar,
        ScrollByContent,
    }

}
